// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#pragma once

#include "actor.hpp"
#include "message.hpp"
#include "planner.hpp"
#include "queue.hpp"
#include "rotor-light-bsp.h"
#include <tuple>
#include <type_traits>
#include <utility>

namespace rotor_light {

namespace details {

/** \struct ChildState
 *  \brief wrapper for holding child states in supervisor */
struct ChildState {
  /** from which state actor is asked to shut self down */
  State down_state = State::off;
};
} // namespace details

/** \struct SupervisorBase
 *  \brief base interface and implementation for all supervisors */
struct SupervisorBase : ActorBase {
  friend struct ActorBase;

  /** the minimum amount of handles, which should
   *  a derived actor class pre-allocate */
  static constexpr size_t min_handlers_amount =
      ActorBase::min_handlers_amount + 2;

  /** convenient alias to the parent struct */
  using Parent = ActorBase;

  /** ctor */
  SupervisorBase(char *backends, size_t backends_count, ActorBase **actors,
                 details::ChildState *states, size_t actors_count);

  /** returns meta-queue */
  inline QueueBase *get_queue() { return queue; }

  void initialize() override;

  /**
   * @brief starts all actors (initialize, wait confirmation etc.)
   * @param poll_timer whether `RefreshTime` message should be
   * send self on idle. When it is, the `process` method will never
   * exit in normal circumstances
   */
  void start(bool poll_timer = false);

  /**
   *  Binds context to the supervisor and all its children. Permanent
   *  actorIds are created and assigned during this phase.
   *
   *  This method should be invoked only once in lifetime
   *  of actors.
   *
   */
  virtual void bind(Context &context, ActorId value = 0);

  uint8_t bind(ActorId initial_value, SupervisorBase *supervisor,
               Context &context) override;

  /**
   *
   *  "Main loop" of the framework, which does messages dispatching.
   *  As the new messages are usually generated during messages dispatching
   *  this method never exits under normal circumstances.
   *
   *  It can exit in two cases:
   *
   *  1. There are no more messages. This might happen if the endless
   *  timer polling is enabled in `start` method. This means, that
   *  you intentionally do that, e.g. to enter into a sleep mode and do
   *  some power-saving before the next event. Platform-specific
   *  sleep and awake codes should be implemented to achive that.
   *
   *  2. Supervisor has terminated, and there is no sense of further
   *  restart attempts. Usually that means, that supervisor did its
   *  best, all restarts (having sense) has been applied without effect,
   *  and nothing can be done, possibly due to unrecoverable hardware
   *  failure; or the supervisor has terminated due to its job is done.
   *  Usually that means board restart or poweroff.
   *
   */
  void process();

  // handlers
  void advance_init() override;
  void advance_start() override;
  void advance_stop() override;

  /** returns pointer to the planner */
  inline PlannerBase *get_planner() { return planner; }

protected:
  /**
   *  refreshes time, processes expired time events and re-schedules
   *  `RefreshTime` message
   *
   *  The method can be overriden to do something useful "on idle".
   *
   */
  virtual void on_refhesh_timer(message::RefreshTime &);

  /** pointer to child actor holding pointers */
  ActorBase **actors;

  /** pointer to child actor states */
  details::ChildState *states;

  /** the amount of child actors */
  size_t actors_count;

  /** pointer to the master queue */
  QueueBase *queue;

  /** pointer to the planner */
  PlannerBase *planner;

  /** function pointer, which can return "now" */
  NowFunction now;

private:
  void on_state_change_ack(message::ChangeStateAck &);
  void on_child_init_failure(size_t actor_index);
  void dispatch(Message &);
  void on_child_down(size_t actor_index);
  void init_child(size_t actor_index);
  void start_actor(size_t actor_index);
  size_t process_watchdog();
  bool check_child_state(State subject, bool skip_self);
};

/** \struct Supervisor
 *  \brief convenient templated base class for supervisor, which
 *  owns (stores) all it's child-actors */
template <size_t HandlersCount, typename... Actors>
struct Supervisor : SupervisorBase {
  static_assert(HandlersCount >= SupervisorBase::min_handlers_amount,
                "no enough handlers");

  Supervisor()
      : SupervisorBase(reinterpret_cast<char *>(&backends), HandlersCount,
                       actors, children_states_holder, children_count + 1) {
    actors[0] = this;
    fill_actor<0>();
  }

  /** returns child actor by indes */
  template <size_t ChildIndex> auto get_child() {
    return &std::get<ChildIndex>(children);
  }

protected:
  /** heterogenious / type-friendly container for child actors (type) */
  using ActorsList = std::tuple<Actors...>;

  /** count of child actors */
  static constexpr size_t children_count = std::tuple_size_v<ActorsList>;

  /** heterogenious / type-friendly container for child actors */
  ActorsList children;

  /** message handlers storage */
  ActorBase::Handler backends[HandlersCount];

  /** pointers to actors, including self. Needed to be processed
   *  uniformly */
  ActorBase *actors[children_count + 1];

  /** child actors states, including self */
  details::ChildState children_states_holder[children_count + 1];

private:
  template <size_t ChildIndex> void fill_actor() {
    if constexpr (ChildIndex < children_count) {
      actors[ChildIndex + 1] = get_child<ChildIndex>();
      fill_actor<ChildIndex + 1>();
    }
  }
};

template <typename Ctx, typename MessageType, bool force, typename... Args>
bool ActorBase::send_impl(size_t queue_index, Args &&...args) {
  auto &queue = supervisor->queue;
  if constexpr (std::is_same_v<Ctx, ctx::thread>) {
    ROTOR_LIGHT_DISABLE_INTERRUPTS();
  }
  auto result =
      queue->put<MessageType, force>(queue_index, std::forward<Args>(args)...);
  if constexpr (std::is_same_v<Ctx, ctx::thread>) {
    ROTOR_LIGHT_ENABLE_INTERRUPTS();
  }
  return result;
}

template <typename Ctx>
EventId ActorBase::add_event(Duration delta, Callback callback, void *data) {
  assert(supervisor->now && supervisor->planner);
  if constexpr (std::is_same_v<Ctx, ctx::thread>) {
    ROTOR_LIGHT_DISABLE_INTERRUPTS();
  }
  auto when = supervisor->now() + delta;
  auto p = supervisor->planner;
  auto result = p->add_event<ctx::interrupt>(when, callback, data);
  if constexpr (std::is_same_v<Ctx, ctx::thread>) {
    ROTOR_LIGHT_ENABLE_INTERRUPTS();
  }
  return result;
}

} // namespace rotor_light
